package net.jonbuck.tassoo.eventmanager;

import net.jonbuck.tassoo.eventmanager.events.TassooEvent;
import net.jonbuck.tassoo.eventmanager.exception.UnsupportedListenerException;

import org.eclipse.core.runtime.ListenerList;

/**
 * <p>
 * <b> This class is the default implementation of the Tassoo Event Manager.</b>
 * It provides all of the features required to manage and orchestrate all of the
 * events and that can be used within Tassoo and the listeners that are needed
 * to make the events do something.
 * </p>
 * 
 * @since 1.0.0
 */
public final class EventManagerImpl implements EventManager {

	/** The simple name of the class which is used for logging purposes. */
	// private static final String CLASS_NAME =
	// EventManagerImpl.class.getSimpleName();

	/**
	 * <p>
	 * <b> The internal instance of the ContactBarEventManager which is returned
	 * to a client using the getInstance() method. </b>
	 * </p>
	 */
	private static EventManager instance;

	/**
	 * <p>
	 * <b>The internal list of registered event types.</b>
	 * </p>
	 */
	private Class<?>[] eventTypes = null;

	/**
	 * The internal list of registered listeners.
	 */
	private ListenerList[] listenerLists = null;

	/**
	 * Default private constructor as the ContactBarEventManager is a singleton
	 * therefore use the getInstance() method to get a handle to this class.
	 */
	private EventManagerImpl() {
		super();
		initialize();
	}

	/**
	 * <p>
	 * <b> Returns an instance of the ContactBarEventManager. </b>
	 * </p>
	 * 
	 * @return the single instance of the ContactBarEventManager.
	 */
	public static EventManager getInstance() {
		if (EventManagerImpl.instance == null) {
			EventManagerImpl.instance = new EventManagerImpl();
		}
		return EventManagerImpl.instance;
	}

	/**
	 * <p>
	 * <b> Adds the listener to the EventManager for the specified event type.
	 * </b>
	 * </p>
	 * 
	 * @param eventType
	 *            the type of event for which this listener is registering.
	 * @param listener
	 *            the listener that wants to be notified when the event is
	 *            fired.
	 * @since 1.0.0
	 */
	public synchronized void addListener(final Class<?> eventType, final Object listener) {
		int eventTypeIndex = findEventTypeIndex(eventType);
		if (eventTypeIndex == -1) {
			int length;
			length = this.eventTypes.length;
			Class<?>[] newEventTypes = new Class<?>[length + 1];
			System.arraycopy(this.eventTypes, 0, newEventTypes, 0, length);
			this.eventTypes = newEventTypes;
			ListenerList[] newListenerLists = new ListenerList[length + 1];
			System.arraycopy(this.listenerLists, 0, newListenerLists, 0, length);
			this.listenerLists = newListenerLists;
			this.eventTypes[length] = eventType;
			this.listenerLists[length] = new ListenerList(0);
			eventTypeIndex = length;
		}
		ListenerList listenerList = this.listenerLists[eventTypeIndex];
		listenerList.add(listener);
	}

	/**
	 * <p>
	 * <b> Notifies the listeners associated with the event type that the event
	 * has occurred. </b>
	 * </p>
	 * 
	 * @param eventType
	 *            the event type whose listeners will be notified of the event.
	 * @since 1.0.0
	 */
	public synchronized void fireEvent(final TassooEvent eventType) {
		int eventTypeIndex = findEventTypeIndex(eventType.getClass());
		if (eventTypeIndex != -1) {
			Object[] listeners = this.listenerLists[eventTypeIndex].getListeners();
			for (int i = 0; i < listeners.length; i++) {
				try {
					eventType.dispatchEvent(listeners[i]);
				} catch (UnsupportedListenerException ule) {

				}
			}
		}
	}

	/**
	 * <p>
	 * <b> Removes the listener associated with the event type from the
	 * EventManager. </b>
	 * </p>
	 * 
	 * @param eventType
	 *            the type of event for which the listener has been registered.
	 * @param listener
	 *            the listener to remove.
	 * @since 1.0.0
	 */
	public void removeListener(final Class<?> eventType, final Object listener) {
		int eventTypeIndex = findEventTypeIndex(eventType);
		if (eventTypeIndex != -1) {
			this.listenerLists[eventTypeIndex].remove(listener);
		}
	}

	/**
	 * <p>
	 * <b> Returns the location of the listeners that are associated with the
	 * event type. </b>
	 * </p>
	 * 
	 * @param eventType
	 *            the event type for which we want the location of it's
	 *            listeners.
	 * @return the location of the listeners that are associated with the event
	 *         type.
	 */
	private int findEventTypeIndex(final Class<?> eventType) {
		if (this.eventTypes != null) {
			for (int i = 0; i < this.eventTypes.length; i++) {
				if (this.eventTypes[i] == eventType) {
					return i;
				}
			}
		}
		return -1;
	}

	/**
	 * <p>
	 * <b> Initialises the Event Manager preparing it for use. </b>
	 * </p>
	 */
	private void initialize() {
		this.listenerLists = new ListenerList[0];
		this.eventTypes = new Class<?>[0];
	}

}
